#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <strings.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <cerrno>
#include <cstring>
#include <functional>
#include <pthread.h>

#include "Comm.hpp"
#include "nocopy.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
#include "Threadpool.hpp"
#include "LockGuard.hpp"
using namespace std;

// using func_t = function<string(string)>;

using task_t = function<void()>; // 任务是一个函数方法

static const uint16_t defaultport = 8888;
static const uint16_t defaultfd = -1;
static const int defaultsize = 1024;
// 聚焦在IO上
class UdpServer : public nocopy
{
public:
    UdpServer(uint16_t port = defaultport)
        : _port(port), _sockfd(defaultfd)
    {
        pthread_mutex_init(&_user_mutex, nullptr);
    }

    void Init()
    {
        // 1. 创建套接字,创建成功后会返回 a ffile descriptor ，出错返回-1
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0) // 创建套接字失败
        {
            lg.LogMessage(Fatal, "socket errno, %d : %s\n", errno, strerror(errno));
            exit(Socket_Err);
        }
        lg.LogMessage(Info, "socket success,sockfd : %d\n", _sockfd);

        // 2. 绑定，指定网络信息 相当于给套接字命名
        struct sockaddr_in local; // 该结构体 需要新加头文件：#include <netinet/in.h>、#include <arpa/inet.h>
        // 补充结构体sockaddr_in：
        bzero(&local, sizeof(local)); // 将指定的空间的大小内存清零
        local.sin_family = AF_INET;   // 协议家族 ，绑定网络通信的信息
        // sin：s：socket in：inet（IP地址！）
        local.sin_port = htons(_port);      // 将string的_port转换成网络序列，通过htons h:host、to、n:net、s:socket
        local.sin_addr.s_addr = INADDR_ANY; // 一般不固定一个ip，而是任意生成一个ip的动态绑定：INADDR_ANY == 0
        // local.sin_addr.s_addr = inet_addr(_ip.c_str());//需要把uint16_t的ip转化成4字节的uint32_t的网络序列的ip，可以通过inet_addr

        // 将刚刚设置好的网络信息和的文件信息进行绑定
        int n = ::bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n != 0)
        {
            lg.LogMessage(Fatal, "bind errno, %d : %s\n", errno, strerror(errno));
            exit(Bind_Err);
        }
        // 绑定完成后就：服务器就已经初始化完成了

        ThreadPool<task_t>::GetInstance()->Start();
    }

    void AddOnlineUser(InetAddr addr)
    {
        LockGuard lockgurad(&_user_mutex);
        for (auto &user : _onlien_user)
        {
            if (addr == user)
                return; // 表示已经存在该用户
        }
        _onlien_user.push_back(addr);

        lg.LogMessage(Debug, "create new a user:%s:%d\n", addr.IP().c_str(), addr.Port());
    }

    void Route(int sock, string &message) // 路由,转发给另一个
    {
        LockGuard lockguard(&_user_mutex);
        for (auto &user : _onlien_user)
        {
            sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr *)&user.GetAddr(), sizeof(user.GetAddr()));
            lg.LogMessage(Debug, "The server sends a %s to %s:%d\n", message.c_str(), user.IP().c_str(), user.Port());
        }
    }

    void Start()
    {
        char buffer[defaultsize];
        // 服务器永远不退出！
        for (;;)
        {
            // 服务器进行udp收发消息
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer); // socklen_t 就是无符号整形

            ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (n > 0)
            {
                InetAddr addr(peer);//用户信息
                // 添加用户
                AddOnlineUser(addr);

                buffer[n] = 0; // 当字符串
                string message = "[";
                message += addr.IP();
                message += ":";
                message += to_string(addr.Port());
                message += "]# ";
                message += buffer;

                task_t task = bind(&UdpServer::Route, this, _sockfd, message);
                ThreadPool<task_t>::GetInstance()->Push(task);
            }
        }
    }
    ~UdpServer()
    {
        pthread_mutex_destroy(&_user_mutex);
    }

private:
    // string _ip;
    uint16_t _port;
    int _sockfd;

    vector<InetAddr> _onlien_user;

    pthread_mutex_t _user_mutex;
    // func_t _OnMessage;//回调
};